/**
 * @ignore
 * 
 * @author 			Zachary Forest Johnson (indiemaps.com/blog)
 * 
 * @description 	E00Parser is a standalone parser for the Arc/Info E00 topological GIS format.
 * 
 * @credits 		E00Parser is based on the (world famous) "ANALYSIS OF ARC EXPORT FILE FORMAT FOR ARC/INFO (REV 6.1.1)" 
 * 						and the Perl extension, Geo::E00, by Alessandro Zummo and Bert Tijhuis (released under the GPL).
 * 
 * @license 		Released under the New BSD License.
 */
package com.indiemaps.mapping.data.parsers.e00
{
	import __AS3__.vec.Vector;
	
	import flash.geom.Point;
	
	/**
	 * A parser for the Arc/Info Exchange topological GIS format.
	 * The main parse() method returns a data object with all information from the e00 ascii data file.
	 * For the structure of the returned data object, see <a href="http://code.google.com/p/e00parser/wiki/Structure">Structure</a> on the Wiki. */
	public class E00Parser
	{
		protected static var lineNumber : int;
		
		public function E00Parser()
		{
		}
		
		protected static function getLine( linesArray : Array ) : String
		{
			if ( lineNumber >= linesArray.length ) return null;
			
			var lineString : String = linesArray[ lineNumber ];
			lineNumber ++;
			return lineString;
		}
		
		/**
		 * Returns a data object with all information from the e00 ascii data file. */
		public static function parse( e00String : String ) : Object
		{
			lineNumber = 0;
			
			var data : Object = {};
			var lines : Array = e00String.split( '\n' );			
			var headline : String = getLine( lines );
			
			// verify the headline is cool
			var headlineRE : RegExp = /^EXP\s+(\d+)\s+(.+)\s*$/m;
			if ( ! headlineRE.test( headline ) ) {
				return null;
			}
			
			var line : String;
			
			//switch to section regexp
			var sectionRE : RegExp = /^([A-Z]{3})\s+(\d+)/m;
			
			while ( line = getLine( lines ) ) {
				// is this a section starting line
				var obj : Object = sectionRE.exec( line );
				if ( obj != null ) {
					
					var section : String = obj[ 1 ];
					var param : String = obj[ 2 ];
					
					switch( section ) {
						case 'ARC': data[ 'arc' ] = parse_arc( lines, param ); break;
						case 'CNT': data[ 'cnt' ] = parse_cnt( lines, param ); break;
						case 'LAB': data[ 'lab' ] = parse_lab( lines, param ); break;
						case 'TOL': data[ 'tol' ] = parse_tol( lines, param ); break;
						case 'TX7': data[ 'tx7' ] = parse_tx7( lines, param ); break;
						case 'LOG': data[ 'log' ] = parse_log( lines, param ); break;
						case 'PRJ': data[ 'prj' ] = parse_prj( lines, param ); break;
						case 'PAL': data[ 'pal' ] = parse_pal( lines, param ); break;
						case 'IFO': data[ 'ifo' ] = parse_ifo( lines, param ); break;
						
					}
				}
			}
			combine( data );

			return data;
		}
		
		/**
		 * Returns a Vector of arc (polyline) objects, one for each unique polyline in the data file.
		 * Includes all constituent points of each arc. */
		protected static function parse_arc( lines : Array, param : String ) : Vector.<Object>
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			var line : String;
			while ( line = getLine( lines ) ) {
				
				// check for termination pattern
				var terminationRE : RegExp = /^\s*-1(\s+0){6}/m;
				if ( terminationRE.test( line ) ) break;
				
				// set header
				var setRE : RegExp = /^\s*(\d+)\s+(\-?\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/m;
				var obj : Object = setRE.exec( line );
				if ( obj != null ) {
					var arc : Object = {
						covNum 		: int( Number( obj[ 1 ] ) ),
						covID		: int( Number( obj[ 2 ] ) ),
						nodeFrom	: int( Number( obj[ 3 ] ) ),
						nodeTo		: int( Number( obj[ 4 ] ) ),
						polyLeft	: int( Number( obj[ 5 ] ) ),
						polyRight	: int( Number( obj[ 6 ] ) ),
						npoints		: int( Number( obj[ 7 ] ) )
					}
					
					var coords 		: Vector.<Number> = new Vector.<Number>();
					var llcoords 	: Vector.<Point> = new Vector.<Point>();
					
					for ( var i : int = 0; i < arc[ 'npoints' ]; )
					{
						var cline : String = getLine( lines );
						
						// see if this is a "2 pairs line"
						var twoPairsRE : RegExp = /^(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+|-]\d+)/m;
						obj = twoPairsRE.exec( cline );
						if ( obj != null ) {
							llcoords[ i ] = new Point( obj[ 1 ], obj[ 2 ] );
							llcoords[ i+1 ] = new Point( obj[ 3 ], obj[ 4 ] );
					
							coords.push( Number( obj[ 1 ] ), Number( obj[ 2 ] ), Number( obj[ 3 ] ), Number( obj[ 4 ] ) );

							i += 2;
							
							continue;
						}
						// one pair line
						var onePairRE : RegExp = /^(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)/m; 
						obj = onePairRE.exec( cline );
						if ( obj != null ) {
							llcoords[ i ] = new Point( obj[ 1 ], obj[ 2 ] );
							
							coords.push( Number( obj[ 1 ] ), Number( obj[ 2 ] ) );

							i += 1;
							
							continue;
						}
						trace( "Unknown ARC pair line:", cline );
					}
					
					if ( ( coords.length / 2 ) != arc[ 'npoints' ] ) {
						trace( "Wrong number of x-y pairs in ARC <>", arc[ 'npoints' ] );
					}
					
					arc[ 'points' ] = coords;
					arc[ 'coord' ] = llcoords;
					
					sets.push( arc );
					
					continue;
				}
				trace( "Unknown ARC set line:", line );
			}
			return sets;
		}
		
		/**
		 * Returns a Vector of centroid objects, one for each polygon in the data file. */
		protected static function parse_cnt( lines : Array, param : String ) : Vector.<Object>
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			var line : String;
			var re : RegExp = /^\s*(\d+)/m;
			var terminationRE : RegExp = /^\s*-1(\s+0){6}/m;
			var centroidRE : RegExp = /^\s*(\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)/m;
			
			var obj : Object;
			
			while ( line = getLine( lines ) ) {
				obj = re.exec( line );
				if ( obj != null ) {
					var cnt : Object = {
						cntID : int( Number( obj[ 1 ] ) )
					};
					
					line = getLine( lines );
					
					if ( terminationRE.test( line ) ) break;
					
					obj = centroidRE.exec( line );
					if ( obj != null ) 
					{
						cnt[ 'x' ] = Number( obj[ 2 ] );
						cnt[ 'y' ] = Number( obj[ 3 ] );
					} else {
						trace( "Unknown CNT line:", line );
					}
					
					sets.push( cnt );
				}
			}
			
			return sets;	
		}
		
		/**
		 * Returns a Vector of LAB (Label Points) objects, one for each polygon in the data file. */
		protected static function parse_lab( lines : Array, param : String ) : Vector.<Object>
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			var line : String;
			
			var terminationRE : RegExp = /^\s*-1\s+0/m;
			var headerRE : RegExp = /^\s*(\d+)\s+(\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)/m;
			
			var obj : Object;
			
			while ( line = getLine( lines ) ) {
				
				// check for termination pattern
				if ( terminationRE.test( line ) ) break;
				
				// set header
				obj = headerRE.exec( line );
				if ( obj != null ) {
					var lab : Object = {
						covID : int( Number( obj[ 1 ] ) ),
						polyID : int( Number( obj[ 2 ] ) ),
						x : Number( obj[ 3 ] ),
						y : Number( obj[ 4 ] )
					};
					
					// Read and throw away the next line
					getLine( lines );
		
					// Store
					sets.push( lab );
							
					continue;
				}
				
				trace( "Unknown lab set line:", line );
			}
			
			return sets;
		}
		
		protected static function parse_tol( lines : Array, param : String ) : Object
		{
			return null;
		}
		
		protected static function parse_tx7( lines : Array, param : String ) : Object
		{
			return null;
		}

		/**
		 * Returns a Vector of Coverage History objects, one for each command recorded by ARC in the file's resource history. */
		protected static function parse_log( lines : Array, param : String ) : Vector.<Object>
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			
			var endRE : RegExp = /^EOL/m;
			var line : String;
			var obj : Object;
			
			while ( line = getLine( lines ) ) {
				if ( endRE.test( line ) ) break;
					
				var log : Object = {
					year 		: int( Number( line.substr( 0, 4 ) ) ),
					month		: int( Number( line.substr( 4, 2 ) ) ),
					day			: int( Number( line.substr( 6, 2 ) ) ),
					hour		: int( Number( line.substr( 8, 2 ) ) ),
					minute		: int( Number( line.substr( 10, 2 ) ) ),
					connecttime	: int( Number( line.substr( 12, 4 ) ) ),
					cputime		: Number( line.substr( 16, 6 ) ),
					iotime		: Number( line.substr( 22, 6 ) ),
					commandline	: line.substr( 28 )
				};
		
				// Read and throw away the next line
				getLine( lines );
				
				sets.push( log );
			}
			return sets;
		}

		protected static function parse_prj( lines : Array, param : String ) : Object
		{
			return null;
		}
		
		/**
		 * Returns a data object, with properties for storing each of the data "files" included in the E00 file.
		 * Six INFO file formats are common: AAT, ACODE, BND, PAT, PCODE, and TIC. */
		protected static function parse_ifo( lines : Array, param : String ) : Object
		{
			var data : Object = {};
			var terminationRE : RegExp = /^EOI/m;
			var infoRE : RegExp = /^(.*)?\.([A-Z]{3})\s+XX\s+(\d+)\s+\d+\s+\d+\s+(\d+)/m;
			var line : String;
			var obj : Object;
			
			while ( line = getLine( lines ) ) {
				if ( terminationRE.test( line ) ) break;
				
				obj = infoRE.exec( line );
				if ( obj != null ) {
					
					var ifo : Object = {
						section : obj[ 1 ],
						name : obj[ 2 ],
						items : obj[ 3 ],
						records : obj[ 4 ],
						lines : 1,
						format : '',
						item : []
					};
					
					var totallength : int = 0;
					
					for ( var i : int = 0; i < ifo[ 'items' ]; i ++ ) {
						var vals : Array = getLine( lines ).split( /\s+/, 6 );
						var itemname : String = vals[ 0 ];
						var widthpos : String = vals[ 1 ];
						var startpos : String = vals[ 2 ];
						var outputformat : String = vals[ 3 ];
						var dummy : String = vals[ 4 ];
						
						// replace '-1' with nothing in widthpos
						widthpos = widthpos.replace( '-1', '' );
						
						// replace '4-1' with nothing in startpos
						startpos = startpos.replace( '4-1', '' );
						
						// replace '-1' with nothing in outputformat
						outputformat = outputformat.replace( '-1', '' );
						
						if (outputformat == '12') outputformat = '14';
						if (outputformat == '5' || outputformat == '6' ) outputformat = '11';
						if (outputformat == '18') outputformat = '24';
						
						
						totallength += Number( outputformat );
						
						ifo[ 'format' ] += 'a' + outputformat + ' ';
						
						if ( totallength > 80 ) {
							ifo[ 'lines' ] ++;
							totallength -= 80;
						}
						
						(ifo[ 'item' ] as Array).push( itemname );
					}	
					data[ ifo[ 'name' ] ] = parse_types( lines, ifo );
				}
			}
			return data;
		}
		
		/**
		 * Returns a Vector of polygon topology objects, one for each unique polygon in the datafile.
		 * Includes a reference to the arcs that make up each polygon. */
		protected static function parse_pal( lines : Array, param : String ) : Vector.<Object>
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			var terminationRE : RegExp = /^\s*-1(\s+0){6}/m;
			var headerRE : RegExp = /^\s*(\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+-]\d+)(\s*[ -]\d+\.\d+E[+|-]\d+)/m;
			var onePairRE : RegExp = /^(\s*[ -]\d+)(\s*[ -]\d+)(\s*[ -]\d+)/m;
			var twoPairRE : RegExp = /^(\s*[ -]\d+)(\s*[ -]\d+)(\s*[ -]\d+)(\s*[ -]\d+)(\s*[ -]\d+)(\s*[ -]\d+)/m;
			
			var line : String;
			var obj : Object;
			
			while ( line = getLine( lines ) ) {
				if ( terminationRE.test( line ) ) break;


				// Set header
				
				//see if single precision
				obj = headerRE.exec( line );
				
				//if it's null, see if it's a 2per
				if ( obj == null ) {
					line += getLine( lines );
					obj = headerRE.exec( line );
					if ( obj == null ) E00Parser.lineNumber --;
				}
				if ( obj != null )
				{
					var pal : Object = {
						npoints	: int( Number( obj[ 1 ] ) ),
						xmin	: Number( obj[ 2 ] ),
						ymin	: Number( obj[ 3 ] ),
						xmax	: Number( obj[ 4 ] ),
						ymax	: Number( obj[ 5 ] )
					};
			
					var points : Array = [];

					for (var i : int = 0; i < pal[ 'npoints' ];)
					{
						// Get a new line

						var cline : String = getLine( lines );

					 	// Check if this is a 2 pairs line
						obj = twoPairRE.exec( cline );

						if ( obj != null )
						{
							points[ i ] = {
								'arc-number' : int( Number( obj[ 1 ] ) ),
								'node-number' : int( Number( obj[ 2 ] ) ),
								'polygon-number' : int( Number( obj[ 3 ] ) )
							};
							
							points[ i+1 ] = {
								'arc-number'  : int( Number( obj[ 4 ] ) ),
								'node-number' : int( Number( obj[ 5 ] ) ),
								'polygon-number' : int( Number( obj[ 6 ] ) )
							};
							
							i += 2;

							continue;
						}

						// 1 pair line
						obj = onePairRE.exec( cline );

						if ( obj != null )
						{
							points[ i ] = {
								'arc-number' : int( Number( obj[ 1 ] ) ),
								'node-number' : int( Number( obj[ 2 ] ) ),
								'polygon-number' : int( Number( obj[ 3 ] ) )
							};
				
							i += 1;

							continue;
						}

						trace( "Unknown PAL pair line:", cline );
					}
					if ( points.length != pal[ 'npoints' ] ) { // unless ((scalar @points) ) eq $pal->{'npoints'};
						trace( "Wrong number of x-y pairs in PAL <>", pal[ 'npoints' ] );
					}
				 

					pal[ 'points' ] = points;

					sets.push( pal );

					continue;
				}
	
				trace( "Unknown PAL set line:", line );	
			}
		
			return sets;
		}

		protected static function fixedWidth( string : String, length : int ) : String
		{
			var formatted : String = string.substr( 0, length );
			while (formatted.length < length) {
				formatted += ' ';
			}
			return formatted;
		}
		
		protected static function unpack( formatString : String, string : String ) : Array
		{
			var unpacked : Array = [];
			var formatList : Array = formatString.split( ' ' );
			var index : int = 0;
			for each ( var formatPart : String in formatList ) {
				var length : Number = Number( formatPart.substr( 1 ) );
				unpacked.push( string.substr( index, length ) );
				index += length;
			}
			
			return unpacked;
		}
		
		protected static function parse_types( lines : Array, ifo : Object ) : Object
		{
			var sets : Vector.<Object> = new Vector.<Object>();
			for ( var i : int = 0; i < ifo[ 'records' ]; i++) {
				
				var types : Object = {};
				var ri : Array = [];
				var itemsnow : int = 0;
				while ( itemsnow < ifo[ 'items' ] ) {
					var line : String = '';
					var j : int;
					var x : String;
					for ( j = 0; j < ifo[ 'lines' ]; j++) {
						x = getLine( lines );
						// TODO: make sure newline characters aren't in the string x above
						line += fixedWidth( x, 80 );
					}
					ri = unpack( ifo[ 'format' ], line );
					
					for ( j = 0; j < ri.length; j++ ) {
						ri[ j ] = String( ri[ j ] ).replace( new RegExp( '^ *', 's' ), '' ); //       s/^ *    //;
						ri[ j ] = String( ri[ j ] ).replace( new RegExp( ' *$', 's' ), '' ); //       s/ *$   //;
						
						types[ ifo[ 'item' ][ j + itemsnow ] ]  = ri[ j ];
					}
					itemsnow += ri.length;
				}
				sets.push( types );
			}
			ifo[ 'data' ] = sets;
			return ifo;
		}

		protected static function combine( dataObject : Object ) : void
		{
			
			//my ($data) = @_;
			var rarc : Object = {};
			var rlab : Object = {};
			
			//my ($rarc, $rlab);
			var nr : int;
			
			if ( dataObject[ 'arc' ] ) {
				nr  = 0;
				var arc : Object = dataObject[ 'arc' ];
				
				for each ( var arcline : Object in arc ) {
					rarc[ arcline[ 'covNum' ] ] = nr++;
				}
			}
		
			if ( dataObject[ 'lab' ] ) {
				nr  = 0;
				var lab : Object = dataObject[ 'lab' ];
		        for each ( var labline : Object in lab ) {
					rlab[ labline[ 'poly-id' ] ] = nr++;
				}
			}
		
			if ( dataObject[ 'ifo' ] ) {
		//___________________________________________________
		// AAT Part
		//___________________________________________________
		
				var el : Array;
				if ( dataObject[ 'ifo' ][ 'AAT' ] ) {	
					var aat : Object = dataObject[ 'ifo' ][ 'AAT' ];
					el = [];
					
				// the next AAT field will be saved in the ARC structure
				// 0 FNODE#            
				// 1 TNODE#           
				// 2 LPOLY#          
				// 3 RPOLY#         
				// 4 LENGTH        saved
				// 5 RRLINE#       < compare with cov_num
				// 6 RRLINE-ID   
				// 7 ..		extra fields
					nr=0;
					for each ( var item : String in aat[ 'item' ] ) { 
						el[ nr++ ] = item;
					}
					for each ( var dline : Object in aat[ 'data' ] ) { 
						nr = rarc[ dline[ el[ 5 ] ] ];
						dataObject[ 'arc' ][ nr ][ el[ 4 ] ] =  dline[ el[ 4 ] ];
						for (var i : int = 7; i < aat[ 'items' ]; i++) {
							dataObject[ 'arc' ][ nr ][ el[ i ] ] =  dline[ el[ i ] ];
						}
					}
					
				}
		//___________________________________________________
		// PAT Part
		//___________________________________________________
				if ( dataObject[ 'ifo' ][ 'PAT' ] ) {
					var pat : Object = dataObject[ 'ifo' ][ 'PAT' ];
					el = [];
				// the next PAT field will be saved in the LAB structure
				// 0 AREA             
				// 1 PERIMETER       
				// 2 PPPOINT#       
				// 3 PPPOINT-ID    
				// 4 ..
					nr = 0;
					for each ( item in pat[ 'item' ] ) { 
						el[ nr++ ] = item;
					}
					for each ( dline in pat[ 'data' ] ) { 
						nr = rlab[ dline[ el[ 2 ] ] ];
						for ( i = 4; i < pat[ 'items' ]; i++ ) {
							dataObject[ 'lab' ][ nr ][ el[ i ] ] =  dline[ el[ i ] ];
						}
					}
				}
			}
		}
		

	}
}